package cn.zwk.service.impl;

import cn.zwk.excel.ColumnIdentifier;
import cn.zwk.excel.ColumnSelector;
import cn.zwk.excel.ParseOptions;
import cn.zwk.excel.ParseResult;
import cn.zwk.excel.ParsedRow;
import cn.zwk.excel.QueryItem;
import cn.zwk.excel.SkipReason;
import cn.zwk.service.SpreadsheetParser;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.enums.CellExtraTypeEnum;
// import removed: ReadCellData is not used with EasyExcel 3.3.x headMap signature
import lombok.extern.slf4j.Slf4j;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

/**
 * 基于 EasyExcel 的解析实现。
 */
@Slf4j
public class EasyExcelSpreadsheetParser implements SpreadsheetParser {

    @Override
    public ParseResult parse(Path file, ParseOptions options) {
        Objects.requireNonNull(file, "file");
        Objects.requireNonNull(options, "options");

        SheetCollector collector = new SheetCollector();

        var readerBuilder = EasyExcel.read(file.toFile(), collector)
                .extraRead(CellExtraTypeEnum.MERGE);

        var sheetBuilder = (options.sheetName() != null && !options.sheetName().isEmpty())
                ? readerBuilder.sheet(options.sheetName())
                : readerBuilder.sheet();

        sheetBuilder
                // headRowNumber 为表头行数，从第0行开始累计到表头所在行
                .headRowNumber(options.headerRowIndex() + 1)
                .doRead();

        Map<Integer, String> indexToHeader = collector.indexToHeader;
        Map<String, Integer> headerToIndex = invertHeader(indexToHeader);

        int keyColIdx = resolveColumnIndex(options.keyColumn(), headerToIndex);
        List<Integer> stmtColIdxs = resolveStatementColumns(options.statementColumns(), headerToIndex);

        List<ParsedRow> rows = new ArrayList<>();
        for (RowData rd : collector.rows) {
            List<SkipReason> reasons = new ArrayList<>();

            if (options.skipMergedCells() && isMergedNonTopLeft(collector.merges, rd.rowIndex, keyColIdx)) {
                reasons.add(SkipReason.MERGED_CELL);
            }

            String key = normalize(rd.data.get(keyColIdx));
            if (options.skipEmptyKeyRows() && isBlank(key)) {
                reasons.add(SkipReason.EMPTY_KEY);
            }

            if (!reasons.isEmpty()) {
                rows.add(new ParsedRow(rd.rowIndex, key, List.of(), reasons));
                continue;
            }

            List<QueryItem> queries = new ArrayList<>();
            for (int colIdx : stmtColIdxs) {
                String v = normalize(rd.data.get(colIdx));
                if (!isBlank(v)) {
                    ColumnIdentifier column = new ColumnIdentifier(colIdx, indexToHeader.get(colIdx));
                    queries.add(new QueryItem(column, v));
                }
            }

            rows.add(new ParsedRow(rd.rowIndex, key, queries, List.of()));
        }

        return new ParseResult(rows);
    }

    private static Map<String, Integer> invertHeader(Map<Integer, String> indexToHeader) {
        Map<String, Integer> m = new HashMap<>();
        for (Map.Entry<Integer, String> e : indexToHeader.entrySet()) {
            m.putIfAbsent(e.getValue(), e.getKey());
        }
        return m;
    }

    private static int resolveColumnIndex(ColumnSelector selector, Map<String, Integer> headerToIndex) {
        if (selector == null) {
            throw new IllegalArgumentException("Column selector is required");
        }
        if (selector.isIndexBased()) {
            return selector.index();
        }
        Integer idx = headerToIndex.get(selector.headerName());
        if (idx == null) {
            throw new IllegalArgumentException("Header not found: " + selector.headerName());
        }
        return idx;
    }

    private static List<Integer> resolveStatementColumns(List<ColumnSelector> selectors, Map<String, Integer> headerToIndex) {
        List<Integer> list = new ArrayList<>();
        if (selectors == null) return list;
        Set<Integer> seen = new HashSet<>();
        for (ColumnSelector s : selectors) {
            int idx = resolveColumnIndex(s, headerToIndex);
            if (seen.add(idx)) {
                list.add(idx);
            }
        }
        return list;
    }

    private static boolean isMergedNonTopLeft(List<CellExtra> merges, int row, int col) {
        Optional<CellExtra> region = findMerge(merges, row, col);
        if (region.isEmpty()) return false;
        CellExtra r = region.get();
        return row != r.getFirstRowIndex();
    }

    private static Optional<CellExtra> findMerge(List<CellExtra> merges, int row, int col) {
        for (CellExtra e : merges) {
            if (e.getType() != CellExtraTypeEnum.MERGE) continue;
            if (row >= e.getFirstRowIndex() && row <= e.getLastRowIndex()
                    && col >= e.getFirstColumnIndex() && col <= e.getLastColumnIndex()) {
                return Optional.of(e);
            }
        }
        return Optional.empty();
    }

    private static String normalize(String s) {
        return s == null ? null : s.trim();
    }

    private static boolean isBlank(String s) {
        return s == null || s.trim().isEmpty();
    }

    private static final class RowData {
        final int rowIndex; // 0-based absolute index
        final Map<Integer, String> data;

        RowData(int rowIndex, Map<Integer, String> data) {
            this.rowIndex = rowIndex;
            this.data = data;
        }
    }

    private static final class SheetCollector extends AnalysisEventListener<Map<Integer, String>> {
        final Map<Integer, String> indexToHeader = new HashMap<>();
        final List<CellExtra> merges = new ArrayList<>();
        final List<RowData> rows = new ArrayList<>();

        @Override
        public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
            // 取最后一个headMap作为表头（当有多行表头时，EasyExcel会多次回调）
            indexToHeader.clear();
            if (headMap == null) return;
            for (Map.Entry<Integer, String> e : headMap.entrySet()) {
                String name = e.getValue();
                if (name != null && !name.trim().isEmpty()) {
                    indexToHeader.put(e.getKey(), name.trim());
                }
            }
        }

        @Override
        public void extra(CellExtra extra, AnalysisContext context) {
            if (extra != null && extra.getType() == CellExtraTypeEnum.MERGE) {
                merges.add(extra);
            }
        }

        @Override
        public void invoke(Map<Integer, String> data, AnalysisContext context) {
            int rowIndex = context.readRowHolder().getRowIndex();
            rows.add(new RowData(rowIndex, new HashMap<>(data)));
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            // no-op
        }
    }
}
